#include "unicode.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <optional>

auto getByteNumOfEncodeUtf8(int value) noexcept -> uint8_t {
    assert(value > 0);

    if (value < 0x7f) {
        return 1;
    } else if (value < 0x7ff) {
        return 2;
    } else if (value < 0xffff) {
        return 3;
    } else if (value < 0x10ffff) {
        return 4;
    }

    return 0;
}
auto getByteNumOfDecodeUtf8(uint8_t value) noexcept -> uint8_t {
    if ((value & 0xc0) == 0x80) {
        return 0;
    } else if ((value & 0xe0) == 0xc0) {
        return 2;
    } else if ((value & 0xf0) == 0xe0) {
        return 3;
    } else if ((value & 0xf8) == 0xf0) {
        return 4;
    }
    return 1;
}

auto encodeToUtf8(uint32_t value) -> std::vector<uint8_t> {
    std::vector<uint8_t> buf;
    if (value < 0x7f) { // ascii
        buf.reserve(1);
        buf[0] = value & 0x7f;
    } else if (value < 0x7ff) { // two bytes
        buf.reserve(2);
        buf[0] += 0xc0 | (value & 0x7c0) >> 6;
        buf[1] += 0x80 | (value & 0x3f);
    } else if (value < 0xffff) { // three bytes
        buf.reserve(3);
        buf[0] += 0xe0 | (value & 0xf000) >> 12;
        buf[1] += 0x80 | (value & 0xfc0) >> 6;
        buf[2] += 0x80 | (value & 0x3f);
    } else if (value < 0x10ffff) { // four bytes
        buf.reserve(4);
        buf[0] += 0xf0 | ((value & 0x1c0000) >> 18);
        buf[1] += 0xe0 | ((value & 0x3f000) >> 12);
        buf[2] += 0x80 | ((value & 0xfc0) >> 6);
        buf[3] += 0x80 | ((value & 0x3f));
    }

    return buf;
}

auto decodeFromUtf8(std::vector<uint8_t> &buf) noexcept -> std::optional<uint32_t> {
    assert(!buf.empty());
    
    if (buf[0] < 0x7f) {
        return buf[0];
    }

    auto size = 0uz;
    uint32_t value = 0;
    if ((buf[0] & 0xe0) == 0xc0) {
        value = buf[0] & 0x1f;
        size = 2;
    } else if ((buf[0] & 0xf0) == 0xe0) {
        value = buf[0] & 0xf;
        size = 3;
    } else if ((buf[0] & 0xf8) == 0xf0) {
        value = buf[0] & 0x7;
        size = 4;
    } else {
        return {};
    }

    if (size > buf.size()) {
        return {};
    }
    for (auto i = 1uz; i < size; i++) {
        if ((buf[i] & 0xc0) != 0x80) {
            return {};
        }

        value = value << 6 | (buf[i] & 0x3f);
    }

    return value;
}
